/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.ui;

import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;

import org.isqlviewer.sql.JdbcService;
import org.isqlviewer.sql.processor.SqlProcessor;
import org.isqlviewer.swing.SwingUtilities;
import org.isqlviewer.swing.action.CustomAction;
import org.isqlviewer.swing.action.OverwriteToggleAction;
import org.isqlviewer.swing.action.SharedActions;
import org.isqlviewer.swing.action.SwingEventManager;
import org.isqlviewer.swing.text.SyntaxHighlighter;
import org.isqlviewer.ui.dnd.TextComponentDropTarget;

/**
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class SqlCommandEditor extends AbstractApplicationView implements CaretListener, FocusListener,
        UndoableEditListener, HyperlinkListener {

    private SqlProcessor sqlProcessor = new SqlProcessor();
    private SyntaxHighlighter commandEditor = new SyntaxHighlighter(320, 240, sqlProcessor);
    private JLabel labelCaretPosition = new JLabel();
    private JLabel labelEditMode = new JLabel();
    private UndoManager undoManager = new UndoManager();
    private boolean executable = false;

    public SqlCommandEditor() {

        this(false);
    }

    public SqlCommandEditor(boolean executable) {

        this.executable = executable;
    }

    public void doLayout(JComponent parentComponent, Preferences preferences, SwingEventManager eventManager) {

        parentComponent.setLayout(new BorderLayout());
        parentComponent.add(new JScrollPane(commandEditor), BorderLayout.CENTER);
        parentComponent.add(createStatusBar(), BorderLayout.SOUTH);
        Keymap map = commandEditor.getKeymap();
        KeyStroke ks = null;
        int eventMask = 0;

        Action a = new OverwriteToggleAction(eventManager);
        if (SwingUtilities.isMacOS()) {
            ks = KeyStroke.getKeyStroke(KeyEvent.VK_I, SwingUtilities.MENU_SHORTCUT_MASK, true);
            map.addActionForKeyStroke(ks, a);
        } else {
            ks = KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0, false);
            map.addActionForKeyStroke(ks, a);
        }

        if (executable) {
            a = new CustomAction("execute-sql-command", SharedActions.EXECUTE_SQL_COMMAND, eventManager);
            eventMask = SwingUtilities.MENU_SHORTCUT_MASK;
            ks = SwingUtilities.createKeyStroke(KeyEvent.VK_ENTER, eventMask);
            a = new TextCommandAction(eventManager, commandEditor);
            map.addActionForKeyStroke(ks, a);

            eventMask = 0;
            ks = SwingUtilities.createKeyStroke(KeyEvent.VK_F9, eventMask);
            map.addActionForKeyStroke(ks, a);

            eventMask = KeyEvent.SHIFT_DOWN_MASK;
            ks = SwingUtilities.createKeyStroke(KeyEvent.VK_F9, eventMask);
            a = new ContextExecutionAction(eventManager, commandEditor);
            map.addActionForKeyStroke(ks, a);

            SqlCommandMouseAdapter adapter = new SqlCommandMouseAdapter();

            commandEditor.addMouseMotionListener(adapter);
            commandEditor.addMouseListener(adapter);
            commandEditor.addHyperlinkListener(this);
        }
    }

    public void configureMenubar(JMenuBar menuBar) {

    }

    public void initializeView() {

        commandEditor.addFocusListener(this);
        commandEditor.addCaretListener(this);

        DropTarget dropTarget = new TextComponentDropTarget(commandEditor);
        dropTarget.setActive(true);
        dropTarget.setDefaultActions(DnDConstants.ACTION_COPY_OR_MOVE);
        commandEditor.setDropTarget(dropTarget);

        commandEditor.setDragEnabled(true);
        commandEditor.getDocument().addUndoableEditListener(this);
        localizeTextComponent(commandEditor, undoManager);
    }

    public void disposeView(Preferences preferences) {

    }

    public void focusGained(FocusEvent e) {

        caretUpdate(null);
    }

    public void focusLost(FocusEvent e) {

    }

    public void caretUpdate(CaretEvent e) {

        Object src = (e == null ? commandEditor : e.getSource());
        int dot = (e == null ? commandEditor.getCaretPosition() : e.getDot());

        int row = 0;
        int column = 0;
        if (src == commandEditor) {
            try {
                row = SwingUtilities.getCaretRow(dot, commandEditor) + 1;
            } catch (BadLocationException e1) {
                row = -1;
            }
            try {
                column = SwingUtilities.getCaretCol(dot, commandEditor) + 1;
            } catch (BadLocationException e1) {
                column = -1;
            }
            String str = " " + row + " : " + column + "[" + dot + "]";
            labelCaretPosition.setText(str);
        }
    }

    public void undoableEditHappened(UndoableEditEvent e) {

        if (e == null || e.getEdit() == null) {
            return;
        }
        UndoableEdit edit = e.getEdit();
        String editName = edit.getPresentationName();
        if (editName.startsWith("style")) {
            return;
        }
        undoManager.addEdit(e.getEdit());
    }

    public void hyperlinkUpdate(HyperlinkEvent e) {

    }

    public void updateServiceKeywords(JdbcService service, String catalog, String schema) throws SQLException {

        Connection connection = service.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();
        sqlProcessor.installServiceKeywords(metaData, catalog, schema);
        commandEditor.setText(commandEditor.getText());
    }

    public String getText() {

        return commandEditor.getText();
    }

    public void setText(String commandText) {

        commandEditor.setText(commandText);
    }

    public void addDocumentListener(DocumentListener listener) {

        commandEditor.getDocument().addDocumentListener(listener);
    }

    private JComponent createStatusBar() {

        JPanel bar = new JPanel(new GridBagLayout());
        Border bdr = BorderFactory.createEtchedBorder();
        labelCaretPosition.setBorder(bdr);
        labelEditMode.setBorder(bdr);

        labelCaretPosition.setVerticalAlignment(SwingConstants.CENTER);
        labelEditMode.setVerticalAlignment(SwingConstants.CENTER);
        labelCaretPosition.setHorizontalAlignment(SwingConstants.CENTER);
        labelEditMode.setHorizontalAlignment(SwingConstants.CENTER);

        Object constraint = null;

        constraint = constrain(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL);
        bar.add(Box.createHorizontalGlue(), constraint);

        constraint = constrain(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE);
        bar.add(labelEditMode, constraint);

        constraint = constrain(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE);
        bar.add(Box.createHorizontalStrut(2), constraint);

        constraint = constrain(3, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE);
        bar.add(labelCaretPosition, constraint);
        return bar;
    }

    private static class ContextExecutionAction extends AbstractAction {

        private static final long serialVersionUID = 6166961766823737471L;
        private SwingEventManager eventManager = null;
        private JTextComponent textComponent = null;

        public ContextExecutionAction(SwingEventManager eventManager, JTextComponent textComponent) {

            this.eventManager = eventManager;
            this.textComponent = textComponent;
        }

        public void actionPerformed(ActionEvent event) {

            String text = textComponent.getText();
            int currentLocation = Math.min(text.length() - 1, textComponent.getCaretPosition());
            currentLocation = Math.max(0, currentLocation);

            int fromIndex = 0;
            int toIndex = text.length() - 1;
            char previousCharacter = 0;
            for (int i = currentLocation; i >= 0; i--) {
                char c = text.charAt(i);
                if (c == '\n' && previousCharacter == '\n') {
                    fromIndex = i;
                    break;
                }
                previousCharacter = c;
            }
            previousCharacter = 0;
            boolean found = false;
            for (int i = currentLocation; i < toIndex; i++) {
                char c = text.charAt(i);
                if (c == '\n' && previousCharacter == '\n') {
                    toIndex = i;
                    found = true;
                    break;
                }
                previousCharacter = c;
            }
            if (!found) {
                toIndex = text.length();
            }
            Object source = event.getSource();
            String contextQuery = text.substring(fromIndex, toIndex).trim();
            if (contextQuery.length() == 0) {
                SwingUtilities.beep();
                return;
            }
            ActionEvent proxiedEvent = new ActionEvent(source, SharedActions.EXECUTE_SQL_COMMAND, contextQuery);
            eventManager.actionPerformed(proxiedEvent);
        }
    }

    private static class TextCommandAction extends AbstractAction {

        private static final long serialVersionUID = 3595177463415569695L;
        private SwingEventManager eventManager = null;
        private JTextComponent textComponent = null;

        public TextCommandAction(SwingEventManager eventManager, JTextComponent textComponent) {

            this.eventManager = eventManager;
            this.textComponent = textComponent;
        }

        public void actionPerformed(ActionEvent event) {

            Object source = event.getSource();
            String commandText = textComponent.getText();
            String currentSelection = textComponent.getSelectedText();

            if (currentSelection != null && currentSelection.trim().length() > 0) {
                ActionEvent proxiedEvent = new ActionEvent(source, SharedActions.EXECUTE_SQL_COMMAND, currentSelection);
                eventManager.actionPerformed(proxiedEvent);
            } else {
                ActionEvent proxiedEvent = new ActionEvent(source, SharedActions.EXECUTE_SQL_COMMAND, commandText);
                eventManager.actionPerformed(proxiedEvent);
            }
        }
    }

}
